package tsc_algorithms.NN_CID_adjust;

import development.DataSets;
import tsc_algorithms.NN_CID;
import utilities.ClassifierTools;
import utilities.InstanceTools;
import weka.classifiers.lazy.kNN;
import weka.core.EuclideanDistance;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.TechnicalInformation;
import weka.core.elastic_distance_measures.DTW;
import weka.core.neighboursearch.PerformanceStats;

/**
 * Adjust for NN_CID 
 * @ unfinished only eu
 * @author jc time 20160425
 * 
 */
public class NN_CID_Adjust extends kNN {
	protected CIDADistance cida;
	protected boolean paramsSet;

	public NN_CID_Adjust() {
		super();
		this.cida = new CIDADistance();
		this.paramsSet = false;
	}

	public void useDTW() {
		cida = new CIDADTWDistance();
	}

	public void setA(int a) {
		this.cida.a = a;
		this.paramsSet = true;
	}

	public TechnicalInformation getTechnicalInformation() {
		TechnicalInformation result = null;
		return result;
	}

	// <editor-fold defaultstate="collapsed" desc="problems used in DAMI paper">
	public static String[] problems = { "FiftyWords", "Adiac", "Beef", "CBF", "ChlorineConcentration", "CinCECGtorso", "Coffee", "CricketX", "CricketY", "CricketZ", "DiatomSizeReduction", "ECG200", "ECGFiveDays", "FaceAll", "FaceFour", "FacesUCR", "Fish", "GunPoint", "Haptics", "InlineSkate", "ItalyPowerDemand", "Lightning2", "Lightning7", "Mallat", "MedicalImages", "Motes", "OSULeaf", "OliveOil", "SonyAIBORobotSurface1", "SonyAIBORobotSurface2", "StarLightCurves", "SwedishLeaf", "Symbols", "SyntheticControl", "Trace", "TwoLeadECG", "TwoPatterns", "Wafer", "WordsSynonyms", "Yoga", "uWaveGestureLibraryX", "uWaveGestureLibraryY", "uWaveGestureLibraryZ" };
	// </editor-fold>

	// <editor-fold defaultstate="collapsed"
	// desc="ACCURACY for CID DTW reported in DAMI paper">
	static double[] reportedResults = { 0.7736, 0.6215, 0.5333, 0.9989, 0.6487, 0.9457, 0.8214, 0.7513, 0.8026, 0.7949, 0.9346, 0.8900, 0.7816, 0.8556, 0.8750, 0.8985, 0.8457, 0.9267, 0.4286, 0.4145, 0.9563, 0.8689, 0.7397, 0.9254, 0.7421, 0.7955, 0.6281, 0.8333, 0.8153, 0.8772, 0.9343, 0.8832, 0.9407, 0.9733, 0.9900, 0.8622, 0.9958, 0.9945, 0.7571, 0.8443, 0.7889, 0.7217, 0.7066 };

	// </editor-fold>

	@Override
	// public void buildClassifier(Instances train) {
	// this.setDistanceFunction(cida);
	// // cid.setInstances(train);
	// super.buildClassifier(train);
	// }
	public void buildClassifier(Instances train) {
		if (!paramsSet) {
			this.cida.crossValidateForA(train);
			paramsSet = true;
		}
		this.setDistanceFunction(this.cida);
		super.buildClassifier(train);
	}

	public static class CIDADistance extends NN_CID.CIDDistance {
		protected int a;
		public boolean sampleTrain = false;

		public CIDADistance() {
			this.a = 0;
		}

		@Override
		public double distance(Instance one, Instance two) {
			return this.distance(one, two, Double.MAX_VALUE);
		}

		@Override
		public double distance(Instance one, Instance two, double cutoff, PerformanceStats stats) {
			return this.distance(one, two, cutoff);
		}

		@Override
		public double distance(Instance first, Instance second, double cutoff) {

			double d = 0;
			// Find the acf terms
			double d1 = 0, d2 = 0;
			double[] data1 = first.toDoubleArray();
			double[] data2 = second.toDoubleArray();
			for (int i = 0; i < first.numAttributes() - 1; i++)
				d += (data1[i] - data2[i]) * (data1[i] - data2[i]);
			d = Math.sqrt(d);
			for (int i = 0; i < first.numAttributes() - 2; i++)
				d1 += (data1[i] - data1[i + 1]) * (data1[i] - data1[i + 1]);
			for (int i = 0; i < first.numAttributes() - 2; i++)
				d2 += (data2[i] - data2[i + 1]) * (data2[i] - data2[i + 1]);
			d1 = Math.sqrt(d1 + 0.001); // This is from theircode
			d2 = Math.sqrt(d2 + 0.001); // This is from theircode
			if (d1 < d2) {
				double temp = d1;
				d1 = d2;
				d2 = temp;
			}
			d = Math.sqrt(d);
			// d = d * (d1 / d2);
			switch (this.a) {
			case 0:
				break;
			case 1:
				d = d * (d1 / d2);
				break;
			case 2:
				d = d * (d2 / d1);
				break;
			default:
				break;
			}
			return d;
		}

		public double[] getNonScaledDistances(Instance first, Instance second) {
			double d = 0;
			// Find the acf terms
			double d1 = 0, d2 = 0;
			double[] data1 = first.toDoubleArray();
			double[] data2 = second.toDoubleArray();
			for (int i = 0; i < first.numAttributes() - 1; i++)
				d += (data1[i] - data2[i]) * (data1[i] - data2[i]);
			d = Math.sqrt(d);
			for (int i = 0; i < first.numAttributes() - 2; i++)
				d1 += (data1[i] - data1[i + 1]) * (data1[i] - data1[i + 1]);
			for (int i = 0; i < first.numAttributes() - 2; i++)
				d2 += (data2[i] - data2[i + 1]) * (data2[i] - data2[i + 1]);
			d1 = Math.sqrt(d1 + 0.001); // This is from theircode
			d2 = Math.sqrt(d2 + 0.001); // This is from theircode
			if (d1 < d2) {
				double temp = d1;
				d1 = d2;
				d2 = temp;
			}
			d = Math.sqrt(d);
			double[] rtnd = { d, d1, d2 };
			return rtnd;
		}

		public double[] crossValidateForA(Instances tr) {
			Instances train = tr;
			if (sampleTrain) {
				tr = InstanceTools.subSample(tr, tr.numInstances() / 10, 0);
			}

			double[] labels = new double[train.numInstances()];
			for (int i = 0; i < train.numInstances(); i++) {
				labels[i] = train.instance(i).classValue();
			}

			int[] a = { 0, 1, 2 };
			
			int n = train.numInstances();
			int k = a.length;
			int[] mistakes = new int[k];

			double[] D;
			double[] L;
			double[] d;
			double dist;
			double d1Dist;
			double d2Dist;

			double[][] LforAll = new double[n][];

			double[] individualDistances;

			for (int i = 0; i < n; i++) {

				D = new double[k];
				L = new double[k];
				for (int j = 0; j < k; j++) {
					D[j] = Double.MAX_VALUE;
				}

				for (int j = 0; j < n; j++) {
					if (i == j) {
						continue;
					}

					individualDistances = this.getNonScaledDistances(train.instance(i), train.instance(j));
					dist = individualDistances[0];
					d1Dist = individualDistances[1];
					d2Dist = individualDistances[2];
					d = new double[k];

					for (int aId = 0; aId < k; aId++) {
						switch (aId) {
						case 0:
							d[aId] = dist;
							break;
						case 1:
							d[aId] = dist * (d1Dist / d2Dist);
							break;
						case 2:
							d[aId] = dist * (d2Dist / d1Dist);
							break;
						default:
							d[aId] = dist;
							break;
						}

						if (d[aId] < D[aId]) {
							D[aId] = d[aId];
							L[aId] = labels[j];
						}
					}
				}

				for (int aId = 0; aId < k; aId++) {
					if (L[aId] != labels[i]) {
						mistakes[aId]++;
					}
				}
				LforAll[i] = L;
			}
			int bsfMistakes = Integer.MAX_VALUE;
			int bsfAId = -1;
			for (int alpha = 0; alpha < k; alpha++) {
				if (mistakes[alpha] < bsfMistakes) {
					bsfMistakes = mistakes[alpha];
					bsfAId = alpha;
				}
			}
			//System.out.println(bsfAId);
			this.a = bsfAId;
			double[] bestAlphaPredictions = new double[train.numInstances()];
			for (int i = 0; i < bestAlphaPredictions.length; i++) {
				bestAlphaPredictions[i] = LforAll[i][bsfAId];
			}
			// System.out.println("a:"+this.a+"b:"+this.b);
			//System.out.println(this.a);
			//System.out.println(bestAlphaPredictions);
			return bestAlphaPredictions;
		}

	}

	public static class CIDADTWDistance extends CIDADistance {
		DTW dtw = new DTW();

		@Override
		public double distance(Instance one, Instance two) {
			return this.distance(one, two, Double.MAX_VALUE);
		}

		@Override
		public double distance(Instance one, Instance two, double cutoff, PerformanceStats stats) {
			return this.distance(one, two, cutoff);
		}

		@Override
		public double distance(Instance first, Instance second, double cutoff) {

			double d = 0;
			// Find the acf terms
			double d1 = 0, d2 = 0;
			double[] data1 = first.toDoubleArray();
			double[] data2 = second.toDoubleArray();

			d = dtw.distance(first, second);
			for (int i = 0; i < first.numAttributes() - 2; i++)
				d1 += (data1[i] - data1[i + 1]) * (data1[i] - data1[i + 1]);
			for (int i = 0; i < first.numAttributes() - 2; i++)
				d2 += (data2[i] - data2[i + 1]) * (data2[i] - data2[i + 1]);
			d1 = Math.sqrt(d1) + 0.001; // This is from theircode
			d2 = Math.sqrt(d2) + 0.001; // This is from theircode
			if (d1 < d2) {
				double temp = d1;
				d1 = d2;
				d2 = temp;
			}
			// d = d * (d1 / d2);
			switch (this.a) {
			case 0:
				break;
			case 1:
				d = d * (d1 / d2);
				break;
			case 2:
				d = d * (d2 / d1);
				break;
			default:
				break;
			}
			return d;
		}

	}

	public static void recreateDTWDistance() {
		int c = 0;
		for (String s : DataSets.ucrNames) {
			kNN k = new kNN(1);
			NN_CID_Adjust k2 = new NN_CID_Adjust();
			k2.useDTW();
			Instances train = ClassifierTools.loadData(DataSets.problemPath + s + "\\" + s + "_TRAIN");
			Instances test = ClassifierTools.loadData(DataSets.problemPath + s + "\\" + s + "_TEST");
			k.buildClassifier(train);
			k2.buildClassifier(train);
			double a1 = ClassifierTools.accuracy(test, k);
			double a2 = ClassifierTools.accuracy(test, k2);
			System.out.println(s + "," + a1 + "," + a2);
			if (a2 > a1)
				c++;
		}
		System.out.println("CID Better on " + c + " out of " + DataSets.ucrNames.length);
	}

	public static void recreateEuclideanDistance() {
		int c = 0;
		int d=0;
		int e=0;
		int f=0;
		for (String s : DataSets.ucrNames) {
			kNN k = new kNN(1);
//			NN_CID_Adjust k2 = new NN_CID_Adjust();
//			k2.setA(0);
			NN_CID_Adjust k3 = new NN_CID_Adjust();
			k3.setA(1);
			NN_CID_Adjust k4 = new NN_CID_Adjust();
			k4.setA(2);
			NN_CID_Adjust k5 = new NN_CID_Adjust();
			Instances train = ClassifierTools.loadData(DataSets.problemPath + s + "\\" + s + "_TRAIN");
			Instances test = ClassifierTools.loadData(DataSets.problemPath + s + "\\" + s + "_TEST");
			k.buildClassifier(train);
//			k2.buildClassifier(train);
			k3.buildClassifier(train);
			k4.buildClassifier(train);
			k5.buildClassifier(train);
			double a1 = ClassifierTools.accuracy(test, k);
//			double a2 = ClassifierTools.accuracy(test, k2);
			double a3 = ClassifierTools.accuracy(test, k3);
			double a4 = ClassifierTools.accuracy(test, k4);
			double a5 = ClassifierTools.accuracy(test, k5);
			System.out.println(s + "," + a1 +  "," + a3 + "," + a4 + "," + a5);
			if (a3 > a1)
				c++;
			if(a3 >= a1)
				d++;
			if (a4 > a1)
				e++;
			if(a4 >= a1)
				f++;
		}
		System.out.println("CID Better on " + c + " out of " + DataSets.ucrNames.length);
		System.out.println("CID Better on " + d + " out of or equal to" + DataSets.ucrNames.length);
		System.out.println("-CID Better on " + e + " out of " + DataSets.ucrNames.length);
		System.out.println("-CID Better on " + f + " out of or equal to" + DataSets.ucrNames.length);
	}

	public static void main(String[] args) {
		recreateEuclideanDistance();
		// recreateDTWDistance();
	}

}
